package tech

import (
	"slices"
	"ssp/set"
)

func (s *Solve) FinnedXWing(g *Grid) (used bool, results []*Result) {
	return g.finnedFish(2)
}

func (s *Solve) FinnedSwordfish(g *Grid) (used bool, results []*Result) {
	return g.finnedFish(3)
}

func (s *Solve) FinnedJellyfish(g *Grid) (used bool, results []*Result) {
	return g.finnedFish(4)
}

func (g *Grid) finnedFish(count int) (used bool, results []*Result) {
	for cand := range 9 {
		if ok, rs := g.finnedFishLines(cand, g.lines[:9], count); ok {
			used = true
			results = append(results, rs...)
		}
		if ok, rs := g.finnedFishLines(cand, g.lines[9:], count); ok {
			used = true
			results = append(results, rs...)
		}
	}
	return
}

func (g *Grid) finnedFishLines(cand int, lines []*House, count int) (used bool, results []*Result) {
	fLines, fCellNumSets := filterLines(lines, cand, 2, count+2)
	if len(fLines) < count+1 {
		return
	}
	setCombs := combine(fCellNumSets, count)
	lineCombs := combine(fLines, count)
	for i := range len(setCombs) {
		fishCellNums := setCombs[i]
		allCellNums := allUion(fishCellNums)

		size := allCellNums.Count()
		if size < count+1 || size > count+2 {
			continue
		}
		if count == 2 && size == 3 && fishCellNums[0].Count() == 2 &&
			fishCellNums[1].Count() == 2 {
			// as turbot fish
			continue
		}

		//fmt.Println(allCellNums.ValueString())
		fishLines := lineCombs[i]
		for _, basicList := range combine(allCellNums.GetList(), count) {
			basic := set.NewSet(basicList...)
			fin := allCellNums.Diff(basic)
			finList := fin.GetList()

			if !isFinsInSameBox(finList) {
				continue
			}

			nearCellNums := getNearFinCellNum(basicList, finList[0])
			if len(nearCellNums) == 0 {
				continue
			}

			ok, finLine := getFinLine(fishLines, fishCellNums, fin)
			if !ok {
				continue
			}

			finLineNum := finLine.cells[0].rowNum
			if finLine.kind == 1 {
				finLineNum = finLine.cells[0].colNum
			}
			rmCells := []*Cell{}
			for i := finLineNum / 3 * 3; i < finLineNum/3*3+3; i++ {
				if slices.Contains(fishLines, lines[i]) {
					continue
				}
				for _, nearCellNum := range nearCellNums {
					if lines[i].cells[nearCellNum].cands.Has(cand) {
						rmCells = append(rmCells, lines[i].cells[nearCellNum])
					}
				}
			}
			if len(rmCells) == 0 {
				continue
			}
			//result.addHelpHouses(fishLines)
			results = g.logFinnedFish(cand, rmCells, fishLines, basicList, finList, finLine, lines, count)
			used = true
			if CHANGE {
				removeCands(set.NewSet(cand), rmCells)
			}
			if !EXHAUSTIVE {
				return
			}
		}
	}
	return
}

func (g *Grid) logFinnedFish(cand int, rmCells []*Cell, fishLines []*House, basicList []int,
	finList []int, finLine *House, lines []*House, count int) (results []*Result) {
	if !OUTPUTRESULT {
		return
	}
	result := &Result{}
	result.addRemoved(set.NewSet(cand), rmCells)
	rcells := []*Cell{}
	for _, line := range fishLines {
		for _, cellNum := range basicList {
			rcells = append(rcells, line.cells[cellNum])
		}
	}
	g1 := result.getHelpCandsGroup()
	g1.add(set.NewSet(cand), rcells)

	rcells = []*Cell{}
	for _, cellNum := range finList {
		rcells = append(rcells, finLine.cells[cellNum])
	}
	g2 := result.getHelpCandsGroup()
	g2.add(set.NewSet(cand), rcells)

	rhouses := []*House{}
	rhouses = append(rhouses, fishLines...)
	result.addHelpHouses(rhouses)

	rhouses = []*House{}
	for _, cellNum := range basicList {
		if lines[0].kind == 0 {
			rhouses = append(rhouses, &g.cols[cellNum])
		} else {
			rhouses = append(rhouses, &g.rows[cellNum])
		}
	}
	result.addHelpHouses(rhouses)

	result.setStrategyName("Finned " + FISHNAMES[count-2])

	results = append(results, result)

	return
}

func getFinLine(fishLines []*House, fishCellNums []set.Set, fin set.Set) (ok bool, finLine *House) {
	finLineCount := 0
	for no, cellNums := range fishCellNums {
		if !cellNums.Intersect(fin).IsNull() {
			finLineCount++
			finLine = fishLines[no]
		}
	}
	if finLineCount != 1 {
		return
	}
	ok = true
	return
}

func getNearFinCellNum(basicList []int, finCellNums int) (nears []int) {
	for _, num := range basicList {
		if num/3 == finCellNums/3 {
			nears = append(nears, num)
		}
	}
	return nears
}

func isFinsInSameBox(finList []int) bool {
	if len(finList) == 2 {
		return finList[0]/3 == finList[1]/3
	}
	return true
}

func allUion(sets []set.Set) (union set.Set) {
	x := sets[0]
	for _, s := range sets[1:] {
		x = x.Union(s)
	}
	return x
}

func filterLines(lines []*House, cand, minCount, maxCount int) (fLines []*House, fCellNumSets []set.Set) {
	for _, line := range lines {
		s := set.NewSet()
		count := 0
		for i, cell := range line.cells {
			if cell.cands.Has(cand) {
				s.Add(i)
				count++
			}
		}
		if count < minCount || count > maxCount {
			continue
		}
		fLines = append(fLines, line)
		fCellNumSets = append(fCellNumSets, s)
	}
	return
}
